/**
 * @license
 * Copyright 2021 Google LLC. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

import {WebIO} from '@gltf-transform/core';
import {KHRONOS_EXTENSIONS} from '@gltf-transform/extensions';
import {metalRough} from '@gltf-transform/functions';
import {html, LitElement} from 'lit';
import {customElement, property, query, state} from 'lit/decorators.js';

import {validationStyles} from '../../../styles.css.js';
import {State} from '../../../types.js';
import {ConnectedLitElement} from '../../connected_lit_element/connected_lit_element';
import {dispatchGltfUrl, getModel, getModelViewer} from '../../model_viewer_preview/reducer.js';

import {resolveExternalResource, validateGltf} from './validation_utils.js';

import type {Report, Message} from './validation_utils';
import {GLTF} from '@google/model-viewer/lib/three-components/gltf-instance/gltf-defaulted';
import {reduxStore} from '../../../space_opera_base.js';

@customElement('me-validation-modal')
export class ValidationModal extends LitElement {
  static styles = [validationStyles];

  @property({attribute: false}) report: Report = {};
  @state() isOpen: boolean = false;

  open() {
    this.isOpen = true;
  }

  close() {
    this.isOpen = false;
  }

  renderTable(color: string, title: string, messages: Message) {
    return html`
<table class="report-table">
  <thead>
    <tr style="background: ${color};">
      <th>${title}</th>
      <th>Message</th>
      <th>Pointer</th>
    </tr>
  </thead>
  <tbody>
    ${messages.map(message => html`
    <tr>
      <td><code>${message.code}</code></td>
      <td>${message.message}</td>
      <td><code>${message.pointer}</code></td>
    </tr>
    `)}
  </tbody>
</table>
`;
  }

  renderMetaData() {
    return html`
<div class="report">
  <h1>Validation report</h1>
  <ul>
    <li><b>Format:</b> glTF ${this.report.info!.version}</li>
    <li><b>Generator:</b> ${this.report.info!.generator}</li>
    <li>
      <b>Stats:</b>
      <ul>
        <li>${this.report.info!.drawCallCount} draw calls</li>
        <li>${this.report.info!.animationCount} animations</li>
        <li>${this.report.info!.totalJointCount} joints</li>
        <li>${this.report.info!.materialCount} materials</li>
        <li>${this.report.info!.totalVertexCount} vertices</li>
        <li>${this.report.info!.totalTriangleCount} triangles</li>
        <li>${this.report.info!.width?.toPrecision(3)} m x-width</li>
        <li>${this.report.info!.height?.toPrecision(3)} m y-height</li>
        <li>${this.report.info!.length?.toPrecision(3)} m z-length</li>
        <li>Extensions used: ${
        this.report.info!.extensionsUsed?.join(', ')}</li>
        ${
        this.report.info!.converted ? html`
        <li><b>KHR_materials_pbrSpecularGlossiness extension was encountered, but is no longer supported. 
        This file has been automatically (and losslessly) converted to Metallic-Roughness.
        Please download the GLB to get the updated version.</b></li>` :
                                      html``}
      </ul>
    </li>
  </ul>
  <hr/>
  <p>
    Report generated by
    <a href="https://github.com/KhronosGroup/glTF-Validator/">KhronosGroup/glTF-Validator</a>
    ${this.report.validatorVersion}.
  </p>
</div>
    `;
  }

  render() {
    if (this.report.info == null) {
      return html``;
    }
    return html`
<paper-dialog id="file-modal" modal ?opened=${this.isOpen}>
  <div class="container">
    <div class="cancel">
      <mwc-button unelevated icon="cancel" @click=${this.close}>
        Close
      </mwc-button>
    </div>
    ${this.renderMetaData()}
    ${
        this.report.issues!.numErrors ?
            this.renderTable('#f44336', 'Error', this.report!.errors!) :
            html``}
   ${
        this.report.issues!.numWarnings ?
            this.renderTable('#f9a825', 'Warning', this.report!.warnings!) :
            html``}
   ${
        this.report.issues!.numHints ?
            this.renderTable('#8bc34a', 'Hint', this.report!.hints!) :
            html``}
    ${
        this.report.issues!.numInfos ?
            this.renderTable('#2196f3', 'Info', this.report!.infos!) :
            html``}
  </div>
</paper-dialog>`;
  }
}

/**
 * Model validator
 */
@customElement('me-validation')
export class Validation extends ConnectedLitElement {
  @query('me-validation-modal#validation-modal')
  validationModal!: ValidationModal;
  @state() gltfUrl?: string;
  @state() rootPath = '';
  @state() fileMap = new Map<string, File>();
  @state() originalGltf?: GLTF;
  @state() report: Report = {};

  @state() converted = false;
  @state() severityTitle: string = '';
  @state() severityColor: string = '';

  async stateChanged(state: State) {
    const {rootPath, originalGltf, gltfUrl, fileMap} = getModel(state);
    if (rootPath != null) {
      this.rootPath = rootPath;
    }

    if (originalGltf != null && gltfUrl != null &&
        this.originalGltf !== originalGltf) {
      if (this.severityTitle = 'Converted') {
        URL.revokeObjectURL(this.gltfUrl!);
      }

      this.originalGltf = originalGltf;
      this.fileMap = fileMap;
      this.gltfUrl = gltfUrl;

      await this.awaitLoad(gltfUrl);

      if (this.severityTitle === 'Converting') {
        // Auto-convert SpecGloss to MetalRough and reload.
        const io = new WebIO().registerExtensions(KHRONOS_EXTENSIONS);
        const doc = await io.read(gltfUrl);
        await doc.transform(metalRough());
        const glb = await io.writeBinary(doc);
        const blob = new Blob([glb], {type: 'application/octet-stream'});
        const fileURL = URL.createObjectURL(blob);
        this.converted = true;
        reduxStore.dispatch(dispatchGltfUrl(fileURL));
        return;
      } else {
        this.converted = false;
      }

      this.countJoints(originalGltf);

      const dimensions = getModelViewer().getDimensions();
      this.report.info = {
        ...this.report?.info,
        width: dimensions.x,
        length: dimensions.z,
        height: dimensions.y
      };
    }
  }

  async awaitLoad(url: string) {
    this.report = await validateGltf(url, (uri: string) => {
      return resolveExternalResource(uri, this.rootPath, this.fileMap);
    });
    this.severityTitle = 'Model Details';
    this.severityColor = '#3c4043';
    if (this.report.issues!.numInfos) {
      this.severityColor = '#2196f3';
      this.severityTitle = 'Info';
    }
    if (this.report.issues!.numHints) {
      this.severityColor = '#8bc34a';
      this.severityTitle = 'Hint';
    }
    if (this.report.issues!.numWarnings) {
      this.severityColor = '#f9a825';
      this.severityTitle = 'Warning';
    }
    if (this.report.issues!.numErrors) {
      this.severityColor = '#f44336';
      this.severityTitle = 'Error';
    }
    if (this.converted) {
      this.report.info!.converted = true;
      this.severityColor = '#8bc34a';
      this.severityTitle = 'Converted';
    }
    if (!!this.report.info?.extensionsUsed?.find(
            (e) => e == 'KHR_materials_pbrSpecularGlossiness')) {
      this.severityColor = '#f9a825';
      this.severityTitle = 'Converting';
    }
  }

  countJoints(gltf: GLTF) {
    const jointSet = new Set();
    if (gltf.skins != null) {
      for (const skin of gltf.skins) {
        for (const joint of skin.joints) {
          jointSet.add(joint);
        }
      }
    }
    this.report.info = {...this.report?.info, totalJointCount: jointSet.size};
  }

  onOpen() {
    this.validationModal.open();
  }

  render() {
    return html`
    ${
        this.severityTitle.length > 0 ? html`
<div style="font-size: 14px; font-weight: 500; margin: 10px 0px;">Validation Report:</div>
<mwc-button unelevated style="align-self: center; padding-top: 10px;"
  @click=${this.onOpen} style="--mdc-theme-primary: ${this.severityColor}">
  ${this.severityTitle}
</mwc-button>` :
                                        html``}
    ${
        this.report ? html`
    <me-validation-modal id="validation-modal" .report=${this.report}>
    </me-validation-modal>
    ` :
                      html``}

    `;
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'me-validation-modal': ValidationModal;
    'me-validation': Validation;
  }
}
